#!/usr/bin/env python
'''
Copyright 2011 Eviatar Bach

This file is part of patient-form-scanning.

patient-form-scanning is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.

patient-form-scanning is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.

You should have received a copy of the GNU General Public License along with
patient-form-scanning. If not, see http://www.gnu.org/licenses/.

patient-form-scanning is an open-source Python-based electronic medical record
(EMR) system, focusing on automatically digitizing records with barcode
recognition and OCR.'''
try:
    #Import standard library modules first, so finally block doesn't fail
    import traceback
    from sys import argv, exit
    import os
    import subprocess
    import datetime
    import ConfigParser
    from xml.dom.minidom import parseString
    import glob
    import shutil
    import re
    error = 0

    def natural_sorted(l): 
        convert = lambda text: int(text) if text.isdigit() else text 
        alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] 
        return sorted(l, key=alphanum_key)
 
    class colours:
        #Idea from joeld from Stack Overflow (http://stackoverflow.com/questions/287871/print-in-terminal-with-colors-using-python)
        OKGREEN = '\033[92m'
        WARNING = '\033[93m'
        FAIL = '\033[91m'
        ENDC = '\033[0m'
        BOLD = '\033[1m'

    #External modules
    import Image
    import zbar

    #TODO: check how scanimage names files; if multiple batches done in one day, continue numbering from last batch

    fnull = open(os.devnull, 'w')
    image = None
    raw = None

    global failed
    failed = 0

    #TODO: Move to separate file
    #Check dependencies
    for dep in ['convert', 'tesseract']:
        if subprocess.call(['which', dep], stdout=fnull, stderr=fnull) == 1:
            print 'Error: Could not find "%s" executable' % dep
            error = 1
            raise SystemExit

    #Setup

    if len(argv) > 1:
        path = os.listdir(argv[1])

    config = ConfigParser.RawConfigParser()
    config.read('options.txt')

    #Make directory

    d = datetime.datetime.today().date().strftime('%b-%d-%Y')
    if not os.path.exists(d):
        os.makedirs(d)
    os.chdir(d)

    #Scanning
    '''
    if len(argv) == 1:
        try:
            dpi = config.get('Options', 'dpi')
        except:
            dpi = 600
        os.system('scanimage --batch --format=tiff --source "ADF Front" --resolution %s' % dpi)
    #    pil = Image.open('out.tif') #ATTENTION
    '''
    try:
        if config.get('Options', 'cropbox').split()[0] != 'None':
            cropbox = config.get('Options', 'cropbox').split()
    except:
        pass

    def file_error():
        print colours.WARNING + each_file + ' failed. Barcode not found.' + colours.ENDC
        global failed
        failed += 1
        '''if not os.path.exists('failed'):
            os.makedirs('failed')'''
        prefix = 1
        while os.path.exists('fail' + str(prefix) + '.pdf'):
            prefix += 1            
        shutil.move(each_file, 'fail' + str(prefix) + '.pdf')
        '''if not os.path.exists('failed/' + each_file):
            shutil.move(each_file, 'failed/' + each_file)
        else:
            while os.path.exists('failed/' + str(prefix) + '_' + each_file):
                prefix += 1
            shutil.move(each_file, 'failed/' + str(prefix) + '_' + each_file)'''

    def scan_image(image):
        #Barcode stuff
        print 'Scanning barcodes...'
        pil = image.convert('L')
        scanner = zbar.ImageScanner()
        scanner.parse_config('disable')
        scanner.parse_config('code128.enable')
        width, height = pil.size
        if 'cropbox' in globals():
            pil.crop(map(eval, cropbox))
        pil.save('tozbar.png')
        try:
            xmlstr = parseString(subprocess.check_output(['../zbarimg', '--xml', 'tozbar.png'])).getElementsByTagName('symbol')
            for index in range(len(xmlstr)):
                if xmlstr.item(index).getAttribute('orientation') == 'DOWN':
                    pil = pil.rotate(180)
        except subprocess.CalledProcessError:
            file_error()
            return
        raw = pil.tostring()
        image = zbar.Image(width, height, 'Y800', raw)
        scanner.scan(image)
        if len(list(image)) == 0:
            file_error()
            '''print 'Barcode not found.'
            os.system('mv %s %s' % (argv[1] + '/' + each_file, argv[1] + '/unsuccessful/' + each_file))'''
        try:
            box = map(int, config.get('Options', 'box').split())
        except:
            box = [-750, -400, 400, 100]

        #OCR stuff
        print 'Performing OCR...'
        for index, symbol in enumerate(image):
            print 'Barcode: ' + symbol.data
            sumdict = dict([(sum(i), pos) for pos, i in enumerate(symbol.location)])
            point = symbol.location[sumdict[max(sumdict)]]
            pil.crop([point[0] + box[0], point[1] + box[1], point[0] + box[2], point[1] + box[3]]).save('temp%s.png' % index)
            subprocess.call(['../deskew', 'temp%s.png' % index, '-o', 'out.png'], stdout=fnull, stderr=fnull)
            subprocess.call(['convert', 'out.png', 'out.tif'], stdout=fnull, stderr=fnull)
            subprocess.call(['tesseract', 'out.tif', 'output', 'nobatch', '../whitelist'], stdout=fnull, stderr=fnull)
            try:
                name = open('output.txt').readlines()[1].strip()
            except:
                file_error() 
            print name, '\n'
            try:
                if not os.path.exists(symbol.data + ' ' + name + '.pdf'):
                    pil.resize((int(pil.size[0] / 4.), int(pil.size[1] / 4.)), Image.ANTIALIAS).save(symbol.data + ' ' + name + '.pdf')
                else:
                    number = 2
                    while os.path.exists(symbol.data + ' ' + name + '_' + str(number) + '.pdf'):
                        number += 1
                    pil.resize((int(pil.size[0] / 4.), int(pil.size[1] / 4.)), Image.ANTIALIAS).save(symbol.data + ' ' + name + '_' + str(number) +  '.pdf')
            except KeyError: #???????
                'Name could not be read.'
        os.remove(each_file)

    if len(glob.glob('out*.tif')) != 0:
        for each_file in natural_sorted(glob.glob('*out*.tif')):
            print 'File: ' + each_file
            scan_image(Image.open(each_file))
    else:
        print colours.FAIL + 'Error: no scanned files' + colours.ENDC
        error = 1

    #Cleanup
    for scrap in [image, fnull, raw]:
        try:
            del(scrap)
        except:
            pass
    if error == 0 and failed == 0:
        print colours.BOLD + colours.OKGREEN + 'Operation completed successfully.' + colours.ENDC
    elif error == 0 and failed != 0:
        print colours.BOLD + colours.WARNING + 'Operation completed with %s failed file(s).' % failed + colours.ENDC
    else:
        raise SystemExit
except:
    error = 1
    print traceback.format_exc()
finally:
    #Routine that handles exiting and cleaning up, even in the case of errors 
    for scrap_file in ['tozbar.png', 'out.tif', 'out.png', 'output.txt'] + glob.glob('temp*.png'):
        try:
            os.remove(scrap_file)
        except:
            pass
    os.chdir('..') #Return to original directory
    if error == 1:
        print colours.BOLD + colours.FAIL + 'Operation failed.' + colours.ENDC
